//
// Created by LiuYou on 2021/8/20.
//

#include <sys/epoll.h>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>
#include <cstdio>
#include <cstdlib>
#include <sys/mman.h>
#include <sys/uio.h>
#include <cstdarg>
#include <cerrno>


#include "HttpConnect.hpp"


static const char* docRoot = "/var/www/html";


static const char* error_500_title = "Internal Error";

static const char* error_500_form = "There was an unusual problem serving the requested file.\n";


static const char* error_400_title = "Bad Request";

static const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";


static const char* error_404_title = "Not Found";

static const char* error_404_form = "The requested file was not found on this server.\n";


static const char* error_403_title = "Forbidden";

static const char* error_403_form = "Forbidden";


static const char* ok_200_title = "OK";


int HttpConnect::epollFd = -1;
int HttpConnect::userCount = 0;


////////////////////////////////////////////////////////////////////////////////////

/*static*/ int setFdNonblocking(int fd) {
    int oldOption = fcntl( fd, F_GETFL );
    int newOption = oldOption | O_NONBLOCK;
    fcntl( fd, F_SETFL, newOption );
    return oldOption;
}

/*static*/ void addFdToEpoll(int epollFd, int fd, bool oneShot) {
    epoll_event epollEvent{};
    epollEvent.data.fd = fd;
    epollEvent.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
    if (oneShot) {
        epollEvent.events |= EPOLLONESHOT;
    }
    setFdNonblocking( fd );
    epoll_ctl( epollFd, EPOLL_CTL_ADD, fd, &epollEvent );
}


/*static*/ void removeFd(int epollFd, int fd) {
    epoll_ctl( epollFd, EPOLL_CTL_DEL, fd, nullptr );
    close( fd );
}

/*static*/ void modFd(int epollFd, int fd, EPOLL_EVENTS events) {
    epoll_event epollEvent{};
    epollEvent.data.fd = fd;
    epollEvent.events = events | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl( epollFd, EPOLL_CTL_MOD, fd, &epollEvent );
}




///////////////////////////////////////////////////////////////////////////


HttpConnect::HttpConnect() = default;

HttpConnect::~HttpConnect() = default;

void HttpConnect::init() {
    checkState = CheckState::CHECK_STATE_REQUEST_LINE;
    linger = false;

    method = Method::GET;
    url = nullptr;
    version = nullptr;
    contentLength = 0;
    host = nullptr;
    startLine = 0;
    checkedIdx = 0;
    readIdx = 0;
    writeIdx = 0;

    memset( readBuf, '\0', READ_BUFFER_SIZE );
    memset( writeBuf, '\0', WRITE_BUFFER_SIZE );
    memset( realFile, '\0', FILE_NAME_LEN );
}

void HttpConnect::init(int sockFd, const sockaddr_in& address) {
    this->sockFd = sockFd;
    this->remoteAddress = address;

    /** 如下两行是为了避免 TIME_WAIT状态, 仅用于调试, 实际使用时应该去掉。*/
    int reuse = 1;
    setsockopt( this->sockFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ));
    addFdToEpoll( epollFd, this->sockFd, true );
    ++userCount;

    init();

}

void HttpConnect::closeConnection(bool realClose) {
    if (realClose && (sockFd != -1)) {
        removeFd( epollFd, sockFd );
        sockFd = -1;
        /** 关闭一个连接时, 将客户总量减 1。*/
        --userCount;
    }
}

void HttpConnect::process() {
    HttpCode readRet = processRead();
    if (readRet == HttpCode::NO_REQUEST) {
        modFd( epollFd, sockFd, EPOLLIN );
        return;
    }
    bool writeRet = processWrite( readRet );
    if (!writeRet) {
        closeConnection();
    }
    modFd( epollFd, sockFd, EPOLLOUT );
}

bool HttpConnect::read() {
    if (readIdx >= READ_BUFFER_SIZE) {
        return false;
    }

    while (true) {
        ssize_t rcvByteNum = recv( sockFd, readBuf + readIdx, READ_BUFFER_SIZE - readIdx, 0 );
        if (rcvByteNum == -1) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                break;
            }
            return false;
        } else if (rcvByteNum == 0) {
            return false;
        }
        readIdx += rcvByteNum;
    }
    return true;
}

bool HttpConnect::write() {
    int sedBytes = writeIdx;
    if (sedBytes == 0) {
        modFd( epollFd, sockFd, EPOLLIN );
        init();
        return true;
    }

    int bytesHaveSend = 0;
    while (true) {
        ssize_t readBytesNum = writev( sockFd, ioVector, ioVectorCount );
        if (readBytesNum <= -1) {
            /* 如果TCP写缓冲没有空间, 则等待下一轮EPOLLOUT事件。虽然在此期间, 服务器无法立刻接受到同一客户的下一个请求,
             * 但这可以保证连接的完整性。*/
            if (errno == EAGAIN) {
                modFd( epollFd, sockFd, EPOLLOUT );
                return true;
            }
            unmap();
            return false;
        }
        sedBytes -= readBytesNum;
        bytesHaveSend += readBytesNum;
        if (sedBytes <= bytesHaveSend) {
            /* 发送HTTP响应成功, 根据HTTP请求中的Connection字段决定是否李隆基关闭连接。*/
            unmap();
            if (linger) {
                init();
                modFd( epollFd, sockFd, EPOLLIN );
                return true;
            } else {
                modFd( epollFd, sockFd, EPOLLIN );
                return false;
            }
        }
    }
}

HttpCode HttpConnect::processRead() {
    LineStatus lineStatus = LineStatus::LINE_OK;
    HttpCode ret = HttpCode::NO_REQUEST;
    char* text = nullptr;

    while (((checkState == CheckState::CHECK_STATE_CONTENT) && (lineStatus == LineStatus::LINE_OK)) ||
           ((lineStatus = parseLine()) == LineStatus::LINE_OK)) {
        text = getLine();
        startLine = checkedIdx;
        printf( "got 1 http line: %s\n", text );

        switch (checkState) {
            case CheckState::CHECK_STATE_REQUEST_LINE: {
                HttpCode httpCode = parseRequestLine( text );
                if (httpCode == HttpCode::BAD_REQUEST) {
                    return HttpCode::BAD_REQUEST;
                }
                break;
            }
            case CheckState::CHECK_STATE_HEADER: {
                HttpCode httpCode = parseHeaders( text );
                if (httpCode == HttpCode::BAD_REQUEST) {
                    return HttpCode::BAD_REQUEST;
                } else if (httpCode == HttpCode::GET_REQUEST) {
                    return doRequest();
                }
                break;
            }
            case CheckState::CHECK_STATE_CONTENT: {
                HttpCode httpCode = parseContent( text );
                if (httpCode == HttpCode::GET_REQUEST) {
                    return doRequest();
                }
                lineStatus = LineStatus::LINE_OPEN;
                break;
            }
            default: {
                return HttpCode::INTERNAL_ERROR;
            }
        }
    }

    return HttpCode::NO_REQUEST;
}

LineStatus HttpConnect::parseLine() {
    char temp;
    for (; checkedIdx < readIdx; ++checkedIdx) {
        temp = readBuf[checkedIdx];
        if (temp == '\r') {

        }
    }
    return LineStatus::LINE_OK;
}

char* HttpConnect::getLine() {
    return readBuf + startLine;
}

HttpCode HttpConnect::parseRequestLine(char* text) {
    url = strpbrk( text, " \t" );
    if (!url) {
        return HttpCode::BAD_REQUEST;
    }
    *url = '\0';

    char* method = text;
    if (strcasecmp( method, "GET" ) == 0) {
        this->method = Method::GET;
    } else {
        return HttpCode::BAD_REQUEST;
    }

    url += strspn( url, " \t" );
    version = strpbrk( url, " \t" );
    if (!version) {
        return HttpCode::BAD_REQUEST;
    }
    *version = '\0';
    version += strspn( version, " \t" );
    if (strcasecmp( version, "HTTP/1.1" ) != 0) {
        return HttpCode::BAD_REQUEST;
    }
    if (strncasecmp( url, "http://", 7 ) == 0) {
        url += 7;
        url = strchr( url, '/' );
    }
    if (!url || url[0] != '/') {
        return HttpCode::BAD_REQUEST;
    }
    checkState = CheckState::CHECK_STATE_HEADER;
    return HttpCode::NO_REQUEST;
}

HttpCode HttpConnect::parseHeaders(char* text) {
    if (text[0] == '\0') {
        /* 如果HTTP请求有消息体, 则还需要读取contentLength字节的消息体, 状态机转移到 CHECK_STATE_CONTENT状态。*/
        if (contentLength != 0) {
            checkState = CheckState::CHECK_STATE_CONTENT;
            return HttpCode::NO_REQUEST;
        }
        /* 否则说明我们已经得到了一个完整的HTTP请求。*/
        return HttpCode::GET_REQUEST;
    }
        /* 处理Connection头部字段。*/
    else if (strncasecmp( text, "Connection:", 11 ) == 0) {
        text += 11;
        text += strspn( text, " \t" );
        if (strcasecmp( text, "keep-alive" ) == 0) {
            linger = true;
        }
    }
        /* 处理Content-Length头部字段。*/
    else if (strncasecmp( text, "Content-Length:", 15 ) == 0) {
        text += 15;
        text += strspn( text, " \t" );
        contentLength = atol( text );
    }
        /* 处理Host头部字段。*/
    else if (strncasecmp( text, "Host:", 5 ) == 0) {
        text += 5;
        text += strspn( text, " \t" );
        host = text;
    } else {
        printf( "oop! unknown header %s\n", text );
    }
    return HttpCode::NO_REQUEST;
}

HttpCode HttpConnect::doRequest() {
    strcpy( realFile, docRoot );
    size_t len = strlen( docRoot );
    strncpy( realFile + len, url, FILE_NAME_LEN - len - 1 );
    if (stat( realFile, &fileStat ) < 0) {
        return HttpCode::NO_RESOURCE;
    }
    if (!(fileStat.st_mode & S_IROTH)) {
        return HttpCode::FORBIDDEN_REQUEST;
    }
    if (S_ISDIR( fileStat.st_mode )) {
        return HttpCode::BAD_REQUEST;
    }
    int fd = open( realFile, O_RDONLY );
    fileAddress = (char*) mmap( 0, fileStat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
    close( fd );
    return HttpCode::FILE_REQUEST;
}

HttpCode HttpConnect::parseContent(char* text) {
    if (readIdx >= (contentLength + checkedIdx)) {
        text[contentLength] = '\0';
        return HttpCode::GET_REQUEST;
    }
    return HttpCode::NO_REQUEST;
}


bool HttpConnect::processWrite(HttpCode code) {
    switch (code) {
        case HttpCode::INTERNAL_ERROR: {
            addStatusLine( 500, error_500_title );
            addHeaders( strlen( error_500_form ));
            if (!addContent( error_500_form )) {
                return false;
            }
            break;
        }
        case HttpCode::BAD_REQUEST: {
            addStatusLine( 400, error_400_title );
            addHeaders( strlen( error_400_form ));
            if (!addContent( error_400_form )) {
                return false;
            }
            break;
        }
        case HttpCode::NO_RESOURCE: {
            addStatusLine( 404, error_404_title );
            addHeaders( strlen( error_404_form ));
            if (!addContent( error_404_form )) {
                return false;
            }
            break;
        }
        case HttpCode::FORBIDDEN_REQUEST: {
            addStatusLine( 403, error_403_title );
            addHeaders( strlen( error_403_form ));
            if (!addContent( error_403_form )) {
                return false;
            }
            break;
        }
        case HttpCode::FILE_REQUEST: {
            addStatusLine( 200, ok_200_title );
            if (fileStat.st_size != 0) {
                addHeaders( fileStat.st_size );
                ioVector[0].iov_base = writeBuf;
                ioVector[0].iov_len = writeIdx;
                ioVector[1].iov_base = fileAddress;
                ioVector[1].iov_len = fileStat.st_size;
                ioVectorCount = 2;
                return true;
            } else {
                const char* okStr = "<html><body></body></html>";
                addHeaders( strlen( okStr ));
                if (!addContent( okStr )) {
                    return false;
                }
            }
        }
        default: {
            return false;
        }
    }

    ioVector[0].iov_base = writeBuf;
    ioVector[0].iov_len = writeIdx;
    ioVectorCount = 1;
    return true;
}

bool HttpConnect::addStatusLine(int status, const char* title) {
    return addResponse( "%s %d %s\r\n", "HTTP/1.1", status, title );
}

bool HttpConnect::addResponse(const char* format, ...) {
    if (writeIdx >= WRITE_BUFFER_SIZE) {
        return false;
    }
    va_list argList;
    va_start( argList, format );
    int len = vsnprintf( writeBuf + writeIdx, WRITE_BUFFER_SIZE - 1 - writeIdx, format, argList );
    if (len >= (WRITE_BUFFER_SIZE - 1 - writeIdx)) {
        return false;
    }
    writeIdx += len;
    va_end( argList );
    return true;
}

bool HttpConnect::addHeaders(size_t contentLength) {
    addContentLength( contentLength );
    addLinger();
    addBlankLine();
    return false;
}

bool HttpConnect::addContentLength(size_t contentLength) {
    return addResponse( "Content-Length: %d\r\n", contentLength );
}

bool HttpConnect::addLinger() {
    return addResponse( "Connection: %s\r\n", (linger == true) ? "keep-alive" : "close" );
}

bool HttpConnect::addBlankLine() {
    return addResponse( "%s", "\r\n" );
}

bool HttpConnect::addContent(const char* content) {
    return addResponse( "%s", content );
}

void HttpConnect::unmap() {
    if (fileAddress) {
        munmap( fileAddress, fileStat.st_size );
        fileAddress = 0;
    }
}




